use std::sync::atomic::AtomicU8;
use std::sync::atomic::Ordering;
use std::cell::UnsafeCell;
use std::sync::Arc;

const WRITE_ONCE_UNINIT:u8 = 0;
const WRITE_ONCE_WRITE_LOCK:u8 = 1;
const WRITE_ONCE_INIT:u8 = 2;

pub struct WriteOnce<T> {
    state:AtomicU8,
    data:UnsafeCell<T>,
}

unsafe impl<T> Send for WriteOnce<T> {}
unsafe impl<T> Sync for WriteOnce<T> {}

impl<T> WriteOnce<T> {
    pub fn new(t:T) -> Self{
        Self {
            state:AtomicU8::new(WRITE_ONCE_UNINIT),
            data:t.into(),
        }
    }

    pub fn new_init(t:T) -> Self{
        Self {
            state:AtomicU8::new(WRITE_ONCE_INIT),
            data:t.into(),
        }
    }

    pub fn new_uninit_arc() -> Arc<Self> {
        unsafe {
            let mut write_once = Arc::<Self>::new_uninit();
            (*Arc::get_mut_unchecked(&mut write_once).as_mut_ptr()).state = AtomicU8::new(WRITE_ONCE_UNINIT);
            write_once.assume_init()
        }
    }

    pub unsafe fn refresh(&self) {
        self.state.store(WRITE_ONCE_UNINIT,Ordering::Relaxed);
    }

    pub fn lock(&self) {
        match self.state.compare_exchange_weak(WRITE_ONCE_UNINIT,WRITE_ONCE_WRITE_LOCK,Ordering::Relaxed,Ordering::Relaxed) {
            Ok(_) => {},
            Err(_) => { panic!("write once cannot be written twice"); },
        }
    }

    pub fn is_init(&self) -> bool {
        self.state.load(Ordering::Relaxed) == WRITE_ONCE_INIT
    }

    pub fn unlock(&self) {
        self.state.store(WRITE_ONCE_INIT,Ordering::Relaxed);
    }

    pub fn write<F:Fn(&mut T)>(&self,f:F) {
        self.lock();
        unsafe { f(&mut *self.data.get()); }
        self.unlock();
    }

    pub fn read(&self) -> &T {
        if self.state.load(Ordering::Relaxed) == WRITE_ONCE_INIT {
            unsafe { &*self.data.get() }
        } else {
            panic!("cannot read write_once before init");
        }
    }
}

use std::thread;
use std::time::Duration;
pub fn write_once_test() {
    let one = WriteOnce::<u8>::new_uninit_arc();
    let two = one.clone();
    let three = one.clone();
    thread::spawn(move||{
        thread::sleep(Duration::new(0,100000));
        println!("read {}",three.read());
    });
    thread::spawn(move||{
        two.write(|num|{
            *num = 5;
        });
    });
    thread::sleep(Duration::new(2,0));
}
